package com.flow.framework.base.service.system.health.impl;

import com.flow.framework.base.properties.FrameworkBaseConfigProperties;
import com.flow.framework.base.properties.component.OperatingSystemConfigProperties;
import com.flow.framework.common.health.ServiceHealthCheckCode;
import com.flow.framework.common.json.JsonArray;
import com.flow.framework.common.pojo.vo.system.ServiceHealthVo;
import com.flow.framework.common.service.system.health.IHealthCheckService;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.system.helper.SystemHealthHelper;
import com.flow.framework.core.system.listener.lifecycle.ISystemLifecycleListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.util.Objects.requireNonNull;

/**
 * 操作系统健康检查
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/12/11
 */
@Slf4j
@RequiredArgsConstructor
public class OperatingSystemHealthCheckServiceImpl implements IHealthCheckService, ISystemLifecycleListener {

    /**
     * List of public, exported interface class names from supported JVM implementations.
     */
    private static final List<String> OPERATING_SYSTEM_BEAN_CLASS_NAMES = Arrays.asList(
            // J9
            "com.ibm.lang.management.OperatingSystemMXBean",
            // HotSpot
            "com.sun.management.OperatingSystemMXBean"
    );

    private final FrameworkBaseConfigProperties frameworkBaseConfigProperties;

    private OperatingSystemMXBean operatingSystemBean;

    private Class<?> operatingSystemBeanClass;

    private Method systemCpuUsage;

    /**
     * @inheritDoc
     */
    @Override
    public void onStartUp() {
        this.operatingSystemBean = ManagementFactory.getOperatingSystemMXBean();
        this.operatingSystemBeanClass = getFirstClassFound(OPERATING_SYSTEM_BEAN_CLASS_NAMES);
        Method getCpuLoad = detectMethod("getCpuLoad");
        this.systemCpuUsage = getCpuLoad != null ? getCpuLoad : detectMethod("getSystemCpuLoad");
    }

    /**
     * @inheritDoc
     */
    @Override
    public List<ServiceHealthVo> check() {
        List<ServiceHealthVo> serviceHealthVos = new ArrayList<>();
        checkCpu(serviceHealthVos);
        checkMemory(serviceHealthVos);
        checkDisk(serviceHealthVos);
        return serviceHealthVos;
    }

    private void checkDisk(List<ServiceHealthVo> serviceHealthVos) {
        try {
            List<OperatingSystemConfigProperties.DiskUsage> diskUsages = frameworkBaseConfigProperties.getOperatingSystem().getDiskUsage();
            if (!VerifyUtil.isEmpty(diskUsages)) {
                List<String> extMessages = new ArrayList<>();
                for (OperatingSystemConfigProperties.DiskUsage diskUsage : diskUsages) {
                    BigDecimal diskUsageDeadLinePercentDecimal = new BigDecimal(diskUsage.getDeadLinePercent());
                    String path = diskUsage.getPath();
                    File file = new File(path);
                    if (!file.exists()) {
                        extMessages.add("path : " + file.getAbsolutePath() + ", file not exist.");
                        continue;
                    }
                    long diskFreeSpace = file.getUsableSpace();
                    long totalSpace = file.getTotalSpace();
                    double usagePercent = (totalSpace - diskFreeSpace) * 1.0d / totalSpace;
                    BigDecimal usagePercentDecimal = new BigDecimal(String.valueOf(usagePercent));

                    if (diskUsageDeadLinePercentDecimal.compareTo(usagePercentDecimal) <= 0) {
                        log.error("disk space not enough.");
                        extMessages.add("path : " + file.getAbsolutePath() + ", storage usage : " + usagePercentDecimal.toString());
                    }
                }
                if (!extMessages.isEmpty()) {
                    serviceHealthVos.add(SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_STORAGE_CODE,
                            JsonArray.toString(extMessages),
                            null));
                } else {
                    serviceHealthVos.add(SystemHealthHelper.healthy(ServiceHealthCheckCode.SERVICE_STORAGE_CODE));
                }
            }
        } catch (Exception e) {
            log.error("check disk space error.", e);
            serviceHealthVos.add(SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_STORAGE_CODE, e.getMessage(), null));
        }
    }

    private void checkMemory(List<ServiceHealthVo> serviceHealthVos) {
        try {
            String memoryUsageDeadLinePercent = frameworkBaseConfigProperties.getOperatingSystem().getMemoryUsageDeadLinePercent();
            if (!VerifyUtil.isEmpty(memoryUsageDeadLinePercent)) {
                BigDecimal memoryUsageDeadLinePercentDecimal = new BigDecimal(memoryUsageDeadLinePercent);
                com.sun.management.OperatingSystemMXBean operatingSystemBean = (com.sun.management.OperatingSystemMXBean) this.operatingSystemBean;
                long totalPhysicalMemorySize = operatingSystemBean.getTotalPhysicalMemorySize();
                long freePhysicalMemorySize = operatingSystemBean.getFreePhysicalMemorySize();
                double usagePercent = (totalPhysicalMemorySize - freePhysicalMemorySize) * 1.0d / totalPhysicalMemorySize;
                BigDecimal usagePercentDecimal = new BigDecimal(String.valueOf(usagePercent));
                if (memoryUsageDeadLinePercentDecimal.compareTo(usagePercentDecimal) >= 0) {
                    serviceHealthVos.add(SystemHealthHelper.healthy(ServiceHealthCheckCode.SERVICE_MEMORY_CODE));
                } else {
                    log.error("memory space not enough.");
                    serviceHealthVos.add(SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_MEMORY_CODE,
                            "memory usage " + usagePercentDecimal.toString(), null));
                }
            }
        } catch (Exception e) {
            log.error("check cpu load error.", e);
            serviceHealthVos.add(SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_MEMORY_CODE, e.getMessage(), null));
        }
    }


    private void checkCpu(List<ServiceHealthVo> serviceHealthVos) {
        try {
            String cpuLoadDeadLinePercent = frameworkBaseConfigProperties.getOperatingSystem().getCpuLoadDeadLinePercent();
            if (!VerifyUtil.isEmpty(cpuLoadDeadLinePercent)) {
                BigDecimal cpuLoadDeadLinePercentDecimal = new BigDecimal(cpuLoadDeadLinePercent);
                if (systemCpuUsage != null) {
                    double cpuUsage = invoke(systemCpuUsage);
                    BigDecimal cpuUsageDecimal = new BigDecimal(String.valueOf(cpuUsage));
                    if (cpuLoadDeadLinePercentDecimal.compareTo(cpuUsageDecimal) >= 0) {
                        serviceHealthVos.add(SystemHealthHelper.healthy(ServiceHealthCheckCode.SERVICE_CPU_LOAD_CODE));
                    } else {
                        serviceHealthVos.add(
                                SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_CPU_LOAD_CODE,
                                        "cpu load " + cpuUsageDecimal.toString(), null)
                        );
                    }
                } else {
                    serviceHealthVos.add(
                            SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_CPU_LOAD_CODE,
                                    "can't find cpu usage", null)
                    );
                }
            }
        } catch (Exception e) {
            log.error("check cpu load error.", e);
            serviceHealthVos.add(
                    SystemHealthHelper.unhealthy(ServiceHealthCheckCode.SERVICE_CPU_LOAD_CODE, e.getMessage(), null)
            );
        }
    }


    private double invoke(Method method) {
        try {
            return method != null ? (double) method.invoke(operatingSystemBean) : Double.NaN;
        } catch (Exception e) {
            return Double.NaN;
        }
    }

    private Method detectMethod(String name) {
        requireNonNull(name);
        if (operatingSystemBeanClass == null) {
            return null;
        }
        try {
            // ensure the Bean we have is actually an instance of the interface
            operatingSystemBeanClass.cast(operatingSystemBean);
            return operatingSystemBeanClass.getMethod(name);
        } catch (Exception e) {
            return null;
        }
    }

    private Class<?> getFirstClassFound(List<String> classNames) {
        for (String className : classNames) {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException ignore) {
            }
        }
        return null;
    }
}
